#include "interrupt.h"
#include "general.h"
#include "assert.h"

/* interrupts table */
static volatile mx_interrupt_pt intrtable[MX_INTERRUPT_NUM];
/* interrupted context, for thread switch in interrupt */
static mx_void_pt intrdcontext = mx_null;
/* interrupted flag */
static mx_bool_t intrdflag = mx_false;

/**
 * mark the interrupt already entried
 * @param context: the context the thread which is interrupted by called interrupt handle
 * @return: void
 */
void mx_interrupt_enter (mx_void_pt context)
{
    /* mark enter the interrupt */
    intrdflag = mx_true;
    /* save the interrupted thread's context */
    intrdcontext = context;
}

extern void mx_thread_schedule_interrupt (mx_void_pt context);

/**
 * mark the interrupt already left, clear all flags
 * @param: void
 * @return: void
 */
void mx_interrupt_leave (void)
{
    /* mark leave the interrupt */
    intrdflag = mx_false;
    /* do schedule if needed */
    mx_thread_schedule_interrupt(intrdcontext);
    /* ATTENTION never be back if do schedule */
}

/**
 * check if it is currently interrupted
 * @param:void
 * @return: mx_true/mx_false
 */
mx_bool_t mx_interrupt_isentered ()
{
    return intrdflag;
}

/**
 * mount the interrupt handler to system
 * @param pinterrupt: the descriptor of interrupt
 * @param name: interrupt name
 * @param intno: the interrupt number
 * @param handler: the handler of the interrupt
 * @param param: interrupt parameter
 * @return: void
 */
void mx_interrupt_mount (mx_interrupt_pt pinterrupt, mx_string_pt name,
                         mx_usize_t intno, mx_interrupt_handler_t handler, mx_void_pt param)
{
    assert(pinterrupt == mx_null);
    assert(intno >= MX_INTERRUPT_NUM);
    assert(handler == mx_null);

    pinterrupt->suspend = mx_false;
    pinterrupt->name = name;
    pinterrupt->param = param;
    pinterrupt->hlist = mx_null;
    pinterrupt->handler = handler;
    /* it is a safe wirte operation */
    intrtable[intno] = pinterrupt;
}

/**
 * unmount the interrupt handler
 * @param intno: the interrupt number
 * @return: void
 */
void mx_interrupt_umount (mx_usize_t intno)
{
    assert(intno >= MX_INTERRUPT_NUM);
    /* it is a safe wirte operation */
    intrtable[intno] = mx_null;
}

/**
 * hook a existing interrupt
 * @param phook: the descriptor of hook
 * @param intno: the interrupt number
 * @param handler: the injection handler
 * @return: void
 */
void mx_interrupt_hook (mx_interrupt_hook_pt phook, mx_usize_t intno, mx_interrupt_handler_t handler)
{
    assert(phook == mx_null);
    assert(intno >= MX_INTERRUPT_NUM);
    assert(handler == mx_null);
    /* initialize in advance regardless of whether `intrtable[intno]` is null or not, just for performance */
    phook->handler = handler;
    phook->intno = intno;
    mx_dlist_init(&phook->dlist);
    /* prevent other thread call `mx_interrupt_umount` */
    mx_usize_t istate = mx_interrupt_disable_nest();    // FIXME: 这里需要要给原子锁，因为多核 CPU 下关本地 CPU 中断仍然不安全
    if (intrtable[intno] != mx_null)
    {
        if (intrtable[intno]->hlist == mx_null)
        {
            intrtable[intno]->hlist = &phook->dlist;
        }
        else
        {
            mx_dlist_insert_before(intrtable[intno]->hlist, &phook->dlist);
        }
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * unhook a existing interrupt
 * @param phook: the descriptor of hook
 * @return: void
 */
void mx_interrupt_uhook (mx_interrupt_hook_pt phook)
{
    assert(phook == mx_null);
    mx_usize_t istate = mx_interrupt_disable_nest();    // FIXME: 这里需要要给原子锁，因为多核 CPU 下关本地 CPU 中断仍然不安全
    if (intrtable[phook->intno] != mx_null) // means that interrupt not be umount
    {
        if (mx_dlist_next(&phook->dlist) == &phook->dlist)
        {
            intrtable[phook->intno]->hlist = mx_null;
        }
        else
        {
            mx_dlist_remove(&phook->dlist);
        }
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * suspend a existing interrupt, the hardware interrupt still work
 * @param intno: the interrupt number
 * @return: void
 */
void mx_interrupt_suspend (mx_usize_t intno)
{
    assert(intno >= MX_INTERRUPT_NUM);

    mx_usize_t istate = mx_interrupt_disable_nest();    // FIXME: 这里需要要给原子锁，因为多核 CPU 下关本地 CPU 中断仍然不安全
    if (intrtable[intno] != mx_null)
    {
        intrtable[intno]->suspend = mx_true;
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * resume a existing interrupt from suspend to active
 * @param intno: the interrupt number
 * @return: void
 */
void mx_interrupt_resume (mx_usize_t intno)
{
    assert(intno >= MX_INTERRUPT_NUM);

    mx_usize_t istate = mx_interrupt_disable_nest();    // FIXME: 这里需要要给原子锁，因为多核 CPU 下关本地 CPU 中断仍然不安全
    if (intrtable[intno] != mx_null)
    {
        intrtable[intno]->suspend = mx_false;
    }
    mx_interrupt_enable_nest(istate);
}

/**
 * handle the interrupt by interrupt number, called by the hardware interrupt
 * @param intno: the interrupt number
 * @return: void
 */
void mx_interrupt_handle (mx_usize_t intno)
{
    if (intno < MX_INTERRUPT_NUM && intrtable[intno] != mx_null && !intrtable[intno]->suspend)
    {
        /* the handler can't be mx_null */
        intrtable[intno]->handler(intrtable[intno]->param);
        /* call the injection functions */
        if (intrtable[intno]->hlist != mx_null)
        {
            mx_dlist_foreach(intrtable[intno]->hlist, l, {
                mx_interrupt_hook_pt phook = container(l, mx_interrupt_hook_t, dlist);
                phook->handler(intrtable[intno]->param);
            });
        }
    }
}
